# -*- coding: utf-8 -*-

from scipy.spatial import distance as dist
import math
import cv2
import numpy as np
from matplotlib import pyplot as plt
import pytesseract

# 定义全局参数
__DEBUG = False


def open_image(image_path):
    """
    以BGR方式打开图片
    :param image_path: 图片文件路径
    :return: 返回OpenCV图片
    """
    return cv2.imread(image_path)


def open_rgb_image(image_path):
    """
    以RGB方式打开图片
    :param image_path: 图片文件路径
    :return: 返回OpenCV图片
    """
    img = cv2.imread(image_path)
    img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    return img_rgb


def open_gray_image(image_path):
    """
    以灰度模式打开图片
    :param image_path: 图片文件路径
    :return: 返回OpenCV图片
    """
    return cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)


def save_image(image_path, img):
    """
    保存图片到文件
    :param image_path: 图片文件路径
    :param img: 图片
    """
    cv2.imwrite(image_path, img)


def show_image(img):
    """
    显示图片
    :param img: 图片
    """
    plt.imshow(img)
    plt.show()


def show_gray_image(img):
    """
    以灰度模式显示图片
    :param img: 图片
    """
    plt.imshow(img, 'gray')
    plt.show()


def show_sub_image(img, pos=None, last_image=False, mode=None):
    """
    以子图方式显示图片
    :param img: 图片
    :param pos: 图片显示的位置
    :param last_image: 是否为本次图片集中最后一张图片，True时显示包括之前所有的图片
    :param mode: gray为灰度模式；None为彩色模式
    """
    if pos is not None:
        plt.subplot(pos)
    plt.imshow(img, mode)
    if last_image:
        plt.show()

def to_gray(img_rgb):
    """
    将RGB图片转换为灰度图片
    :param img_rgb: RGB格式图片
    :return: 返回灰度格式图片
    """
    return cv2.cvtColor(img_rgb, cv2.IMREAD_GRAYSCALE) 


def rotate(img, angle):
    """
    旋转图片
    :param img: 图片
    :param angle: 旋转角度
    :return: 返回旋转后的OpenCV图片
    """
    h, w, _ = img.shape
    center = (w // 2, h // 2)
    mtx = cv2.getRotationMatrix2D(center, angle, 1.0)
    # cos = np.abs(mtx[0, 0])
    # sin = np.abs(mtx[0, 1])
    # nW = int((h * sin) + (w * cos))
    # nH = int((h * cos) + (w * sin))
    # mtx[0, 2] += (nW / 2) - center[0]
    # mtx[1, 2] += (nH / 2) - center[1]

    img_copied = img.copy()
    img_rotated = cv2.warpAffine(img_copied, mtx, (w, h),
                                 flags=cv2.INTER_AREA, borderMode=cv2.BORDER_WRAP)
    return img_rotated


def calc_distance(pt0, pt1):
    """
    计算两点之间的距离
    :param pt0: 点0
    :param pt1: 点1
    :return: 返回两点之间的距离
    """
    return np.linalg.norm(pt0 - pt1)


def calc_slope_angle(line):
    """
    计算直线与X轴之间的夹角
    :param line: 直线[x1, y1, x2, y2]
    :return: 返回角度
    """
    x1, y1, x2, y2 = line
    if x1 == x2:
        return 90.0
    theta = math.atan2((y2 - y1), (x2 - x1))
    angle = theta * (180 / math.pi)
    return angle


def get_outer_contour(contours, min_area=0):
    """
    找到最外层的轮廓
    :param contours: 轮廓列表
    :param min_area: 轮廓最小面积
    :return: 返回最外层的轮廓
    """
    outer_contour = contours[0]
    x0, y0, w0, h0 = cv2.boundingRect(outer_contour)
    for i in range(1, len(contours)):
        contour = contours[i]
        x, y, w, h = cv2.boundingRect(contour)
        if w * h > min_area and w * h > w0 * h0:
            outer_contour = contour
            x0, y0, w0, h0 = x, y, w, h
    if w0 * h0 < min_area:
        return None
    return x0, y0, w0, h0


def get_longest_line(lines):
    """
    获取最长的直线
    :param lines: 直线坐标数组，[[x1, y1, x2, y2], [...],...]
    :return: 返回最长的直线和直线长度
    """
    the_line = lines[0]
    max_length = calc_distance(the_line[:2], the_line[2:])
    for i in range(1, len(lines)):
        line = lines[i]
        length = calc_distance(line[:2], line[2:])
        if length > max_length:
            the_line = line
            max_length = length
    return the_line, max_length


def get_correct_angle(lines, min_length):
    """
    获取表格与X轴的夹角
    :param lines: 直线坐标数组，[[x1, y1, x2, y2], [...],...]
    :param min_length: 直线最小长度
    :return: 返回旋转角度
    """
    cnt, sum_angle = 0, 0.0
    for i in range(0, len(lines)):
        line = lines[i]
        # 线长必须大于最小长度
        distance = calc_distance(line[:2], line[2:])
        if distance < min_length:
            continue
        angle = calc_slope_angle(line)
        sum_angle += angle
        cnt += 1
    avg_angle = 0.0
    if cnt > 0:
        avg_angle = sum_angle / cnt
    return avg_angle


def merge_lines(lines, img_width, min_height=0, min_margin=50, line_interval=100):
    """
    合并竖线列表中合并出直线
    :param lines: 直线集合
    :param img_width: 图片宽度
    :param min_height: 直线的最小高度
    :param margin: 直线离开最有两边的最小边距
    :param line_interval: 直线间的最小间隔
    :return: 返回绘制后的OpenCV图像
    """
    result_lines = []
    line0 = None

    # 根据x1排序
    lines.sort(key=lambda l: l[0])
    for x1, y1, x2, y2 in lines:
        if abs(y2 - y1) < min_height:
            continue
        if x1 <= min_margin or x2 + min_margin >= img_width:
            continue

        if line0 is None or (x1 - line0[0] > line_interval):
            line0 = [x1, y1, x2, y2]
            result_lines.append(line0)
    return result_lines


def recognize_id(img_cell):
    """
    识别id列
    :param img: id列图片
    """
    return pytesseract.image_to_string(img_cell, lang='eng', config='--psm 7 number')


def median(values):
    """
    计算数组的中位数
    :param values: 数组
    """
    length = len(values)
    values.sort()
    pos = length // 2
    if (length % 2)== 1:
        med_value = values[pos]
    else:
        med_value = (values[pos]+values[pos-1])/2
    return med_value 


def cos_dist(a, b):
    if len(a) != len(b):
        return None
    part_up = 0.0
    a_sq = 0.0
    b_sq = 0.0
    for a1, b1 in zip(a, b):
        part_up += a1*b1
        a_sq += a1**2
        b_sq += b1**2
    part_down = math.sqrt(a_sq*b_sq)
    if part_down == 0.0:
        return None
    else:
        return part_up / part_down


# this function is confined to rectangle
def order_points(pts):
    # sort the points based on their x-coordinates
    xSorted = pts[np.argsort(pts[:, 0]), :]

    # grab the left-most and right-most points from the sorted
    # x-roodinate points
    leftMost = xSorted[:2, :]
    rightMost = xSorted[2:, :]

    # now, sort the left-most coordinates according to their
    # y-coordinates so we can grab the top-left and bottom-left
    # points, respectively
    leftMost = leftMost[np.argsort(leftMost[:, 1]), :]
    (tl, bl) = leftMost

    # now that we have the top-left coordinate, use it as an
    # anchor to calculate the Euclidean distance between the
    # top-left and right-most points; by the Pythagorean
    # theorem, the point with the largest distance will be
    # our bottom-right point
    D = dist.cdist(tl[np.newaxis], rightMost, "euclidean")[0]
    (br, tr) = rightMost[np.argsort(D)[::-1], :]

    # return the coordinates in top-left, top-right,
    # bottom-right, and bottom-left order
    return np.array([tl, bl, br, tr], dtype="int32")


def order_points_quadrangle(pts):
    # sort the points based on their x-coordinates
    xSorted = pts[np.argsort(pts[:, 0]), :]

    # grab the left-most and right-most points from the sorted
    # x-roodinate points
    leftMost = xSorted[:2, :]
    rightMost = xSorted[2:, :]

    # now, sort the left-most coordinates according to their
    # y-coordinates so we can grab the top-left and bottom-left
    # points, respectively
    leftMost = leftMost[np.argsort(leftMost[:, 1]), :]
    (tl, bl) = leftMost

    # now that we have the top-left and bottom-left coordinate, use it as an
    # base vector to calculate the angles between the other two vectors

    vector_0 = np.array(bl-tl)
    vector_1 = np.array(rightMost[0]-tl)
    vector_2 = np.array(rightMost[1]-tl)

    angle = [np.arccos(cos_dist(vector_0, vector_1)), np.arccos(cos_dist(vector_0, vector_2))]
    (br, tr) = rightMost[np.argsort(angle), :]

    # return the coordinates in top-left, top-right,
    # bottom-right, and bottom-left order
    return np.array([tl, tr, br, bl], dtype="float32")


if __name__ == '__main__':
    im = open_image('images/1.jpg')
    show_image(im)

    img1 = open_image('images/1.jpg')
    show_sub_image(img1, 131)
    img2 = open_image('images/1.jpg')
    show_sub_image(img1, 132)
    img3 = open_image('images/1.jpg')
    show_sub_image(img1, 133, True)
